#include "Cache.h"
#include "GeoUtil.h"
#include "GLTFParser.h"
#include <limits>

std::mutex Cache::s_modelMutex;
std::mutex Cache::s_lineMutex;
CVector<ModelArg> Cache::s_curTmpModelArgs;
CVector<LineArg> Cache::s_curTmpLineArgs;
bool Cache::s_isReceivingDataEnabled = true;

LineArg Cache::s_defLineArg;
ModelArg Cache::s_defModelArg;

CMap<LineTag, LineArg> Cache::s_lineArgMap;
CMap<int, ModelArg> Cache::s_modelArgMap;
ZhicheArg Cache::s_zhicheArg;
Status Cache::s_status;
sq::Status Cache::s_sqStatus;

bool Cache::s_isZhicheDirty = false;
bool Cache::s_isNewLinesRecved = false;
bool Cache::s_isNewModelsRecved = false;

/**@note load zhiche-model at a new thread (not RenderingThread) to avoid no-other-received-models-shown when app startup.*/
void Cache::startLoadingZhicheAtNewThread(int delayMs) {
	GLTFParser::startAsyncParsingZhiche([] {
		const auto &info = GLTFParser::zhicheInfo();
		s_zhicheArg.width = info.width;
		s_zhicheArg.length = info.length;
	}, delayMs);
}

/**
* @brief precondition: in front of zhiche
*/
int Cache::getFrontModelIdClosestToOrigin() {
	int ID = -1;
	auto minDistSquare = std::numeric_limits<float>::max();
	for (const auto& arg : s_modelArgMap) {
		const auto& Model = arg.value;
		const auto& Pos = Model.pos;
		if (Pos.z > 0)/**@note SQ: z is aligned to the rear of the model*/
			continue;
		float _distSquare = Pos.x * Pos.x + Pos.z * Pos.z;
		if (_distSquare < minDistSquare) {
			minDistSquare = _distSquare;
			ID = arg.key;
		}
	}
	return ID;
}

CVector<int> Cache::getRangeModelIDs(float hRadius, float vRadius, HDir towards) {
	CVector<int> ids;
	for (const auto& arg : s_modelArgMap) {
		const auto& Pos = arg.value.pos;
		if (abs(Pos.z) < vRadius) {
			if (HDir::LEFT == towards && Pos.x < 0) {
				if (-Pos.x < hRadius)
					ids << arg.key;
			} else if (HDir::RIGHT == towards && Pos.x > 0) {
				if (Pos.x < hRadius)
					ids << arg.key;
			}
		}
	}
	return ids;
}

void Cache::setSizeForModels(CVector<ModelArg>& modelArgs) {
	for (auto& modelArg : modelArgs) {
		if (ModelType::INVALID == modelArg.type)
			continue;
		if (modelArg.width > 0.1 && modelArg.length > 0.1)// already set valid size
			continue;
		const auto& info = GLTFParser::info(modelArg.type);
		modelArg.width = info.width;
		modelArg.length = info.length;
	}
}

void Cache::setTagsForValidLines(CVector<LineArg>& lines) {
	if (lines.empty())
		return;
	std::sort(lines.begin(), lines.end(), [](const LineArg& a, const LineArg& b) { return a.eq.C[0] < b.eq.C[0]; });// sort C0 ASC
	int rightLinesNum = 0;
	for (auto& line : lines) {
		if (line.eq.C[0] < 0) ++rightLinesNum;
		else break;
	}
	int leftLinesNum = lines.size() - rightLinesNum;
	for (int i = 0; i < leftLinesNum; i++) {
		auto& tag = lines[rightLinesNum + i].tag;
		switch(i) {
			case 0: tag = LineTag::L; break;
			case 1: tag = LineTag::L2; break;
			case 2: tag = LineTag::L3; break;
			case 3: tag = LineTag::L4; break;
			case 4: tag = LineTag::L5; break;
			case 5: tag = LineTag::L6; break;
			case 6: tag = LineTag::L7; break;
			case 7: tag = LineTag::L8; break;
			case 8: tag = LineTag::L9; break;
			default: break;
		}
	}
	for (int i = 0; i < rightLinesNum; i++) {
		auto& tag = lines[i].tag;
		switch(rightLinesNum - 1 - i) {
			case 0: tag = LineTag::R; break;
			case 1: tag = LineTag::R2; break;
			case 2: tag = LineTag::R3; break;
			case 3: tag = LineTag::R4; break;
			case 4: tag = LineTag::R5; break;
			case 5: tag = LineTag::R6; break;
			case 6: tag = LineTag::R7; break;
			case 7: tag = LineTag::R8; break;
			case 8: tag = LineTag::R9; break;
			default: break;
		}
	}
}

ModelType ssqk_adjustModelType(ModelType type, HDir dir) {// M:middle lane, L:left lane, R:right lane
	if (dir == HDir::LEFT) {
		switch (type) {
			case ModelType::Car: return ModelType::Car_L;
			case ModelType::Bus: return ModelType::Bus_L;
			case ModelType::Truck: return ModelType::Truck_L;
			case ModelType::Moto: return ModelType::Moto_L;
			case ModelType::Bike: return ModelType::Bike_L;
			case ModelType::Person: return ModelType::Person_L;
			case ModelType::Tricycle: return ModelType::Tricycle_L;
		}
	} else if (dir == HDir::RIGHT) {
		switch (type) {
			case ModelType::Car: return ModelType::Car_R;
			case ModelType::Bus: return ModelType::Bus_R;
			case ModelType::Truck: return ModelType::Truck_R;
			case ModelType::Moto: return ModelType::Moto_R;
			case ModelType::Bike: return ModelType::Bike_R;
			case ModelType::Person: return ModelType::Person_R;
			case ModelType::Tricycle: return ModelType::Tricycle_R;
		}
	}
	return type;
}

/*********************************** SSQK *********************************************/
/*********************************** SSQK *********************************************/
Pair<P, ModelType> ssqk_makeModelCentered(const P& modelPos, ModelType modelType, const LineArg& l2Arg, const LineArg& lArg, const LineArg& rArg, const LineArg& r2Arg) {
	P p = modelPos;
	ModelType type = modelType;
	const float X = p.x;
	const float Z = p.z;
	float l2X = InvalidNum, lX = InvalidNum, rX = InvalidNum, r2X = InvalidNum;
	if (l2Arg.isValid()) l2X = GeoUtil::getXByEquation(l2Arg.eq, Z);
	if (lArg.isValid()) lX = GeoUtil::getXByEquation(lArg.eq, Z);
	if (rArg.isValid()) rX = GeoUtil::getXByEquation(rArg.eq, Z);
	if (r2Arg.isValid()) r2X = GeoUtil::getXByEquation(r2Arg.eq, Z);
	if (l2X != InvalidNum && lX != InvalidNum && (X > l2X && X < lX)) {
		p.x = (l2X + lX) / 2;
		type = ssqk_adjustModelType(modelType, HDir::LEFT);
	} else if (lX != InvalidNum && rX != InvalidNum && (X > lX && X < rX)) {
		p.x = (lX + rX) / 2;
		type = modelType;
	} else if (rX != InvalidNum && r2X != InvalidNum && (X > rX && X < r2X)) {
		p.x = (rX + r2X) / 2;
		type = ssqk_adjustModelType(modelType, HDir::RIGHT);
	}
	return MakePair(p, type);
}

void Cache::adjustModelForSSQK(CVector<ModelArg>& modelArgs) {
	const auto& l2Arg = lineArg(LineTag::L2);
	const auto& lArg = lineArg(LineTag::L);
	const auto& rArg = lineArg(LineTag::R);
	const auto& r2Arg = lineArg(LineTag::R2);
	for (auto& model : modelArgs) {
		if (ModelType::INVALID == model.type)
			continue;
		float limitStartZ = DataFilter::get()->bottomZ() + 0.1;
		float limitEndZ = DataFilter::get()->skylineZ() - 0.1;
		float t = (model.pos.z - limitStartZ) / (limitEndZ - limitStartZ);
		t = fmaxf(0.0f, fminf(1.0f, t));// limit t range:[0, 1]
		model.pos.x *= 1 - 0.7 * t;

		auto posAndTypePair = ssqk_makeModelCentered(model.pos, model.type, l2Arg, lArg, rArg, r2Arg);
		model.pos = posAndTypePair.key;
		model.type = posAndTypePair.value;
	}
}